%%-*- mode: erlang -*-
%% emqx_auth_ldap config mapping

{mapping, "auth.ldap.servers", "emqx_auth_ldap.ldap", [
  {default, "127.0.0.1"},
  {datatype, string}
]}.

{mapping, "auth.ldap.port", "emqx_auth_ldap.ldap", [
  {default, 389},
  {datatype, integer}
]}.

{mapping, "auth.ldap.pool", "emqx_auth_ldap.ldap", [
  {default, 8},
  {datatype, integer}
]}.

{mapping, "auth.ldap.bind_dn", "emqx_auth_ldap.ldap", [
  {datatype, string},
  {default, "cn=root,dc=emqx,dc=io"}
]}.

{mapping, "auth.ldap.bind_password", "emqx_auth_ldap.ldap", [
  {datatype, string},
  {default, "public"}
]}.

{mapping, "auth.ldap.timeout", "emqx_auth_ldap.ldap", [
  {default, "30s"},
  {datatype, {duration, ms}}
]}.

{mapping, "auth.ldap.ssl", "emqx_auth_ldap.ldap", [
  {default, false},
  {datatype, {enum, [true, false]}}
]}.

{mapping, "auth.ldap.ssl.certfile", "emqx_auth_ldap.ldap", [
  {datatype, string}
]}.

{mapping, "auth.ldap.ssl.keyfile", "emqx_auth_ldap.ldap", [
  {datatype, string}
]}.

{mapping, "auth.ldap.ssl.cacertfile", "emqx_auth_ldap.ldap", [
  {datatype, string}
]}.

{mapping, "auth.ldap.ssl.verify", "emqx_auth_ldap.ldap", [
  {default, verify_none},
  {datatype, {enum, [verify_none, verify_peer]}}
]}.

{mapping, "auth.ldap.ssl.server_name_indication", "emqx_auth_ldap.ldap", [
  {datatype, string}
]}.

{translation, "emqx_auth_ldap.ldap", fun(Conf) ->
    A2N = fun(A) -> case inet:parse_address(A) of {ok, N} -> N; _ -> A end end,
    Servers = [A2N(A) || A <- string:tokens(cuttlefish:conf_get("auth.ldap.servers", Conf), ",")],
    Port = cuttlefish:conf_get("auth.ldap.port", Conf),
    Pool = cuttlefish:conf_get("auth.ldap.pool", Conf),
    BindDN = cuttlefish:conf_get("auth.ldap.bind_dn", Conf),
    BindPassword = cuttlefish:conf_get("auth.ldap.bind_password", Conf),
    Timeout = cuttlefish:conf_get("auth.ldap.timeout", Conf),
    Filter = fun(Ls) -> [E || E = {_, V} <- Ls, V /= undefined]end,
    SslOpts = fun() ->
                [{certfile, cuttlefish:conf_get("auth.ldap.ssl.certfile", Conf)},
                 {keyfile, cuttlefish:conf_get("auth.ldap.ssl.keyfile", Conf)},
                 {cacertfile, cuttlefish:conf_get("auth.ldap.ssl.cacertfile", Conf, undefined)},
                 {verify, cuttlefish:conf_get("auth.ldap.ssl.verify", Conf, undefined)},
                 {server_name_indication, case cuttlefish:conf_get("auth.ldap.ssl.server_name_indication", Conf, undefined) of
                                            "disable" -> disable;
                                            "" -> undefined;
                                            SNI -> SNI
                                          end}]
              end,
    Opts = [{servers, Servers},
            {port, Port},
            {timeout, Timeout},
            {bind_dn, BindDN},
            {bind_password, BindPassword},
            {pool, Pool},
            {auto_reconnect, 2}],
    case cuttlefish:conf_get("auth.ldap.ssl", Conf) of
        true  -> [{ssl, true}, {sslopts, Filter(SslOpts())}|Opts];
        false -> [{ssl, false}|Opts]
    end
end}.

{mapping, "auth.ldap.device_dn", "emqx_auth_ldap.device_dn", [
  {default, "ou=device,dc=emqx,dc=io"},
  {datatype, string}
]}.

{mapping, "auth.ldap.match_objectclass", "emqx_auth_ldap.match_objectclass", [
  {default, "mqttUser"},
  {datatype, string}
]}.

{mapping, "auth.ldap.custom_base_dn", "emqx_auth_ldap.custom_base_dn", [
  {default, "${username_attr}=${user},${device_dn}"},
  {datatype, string}
]}.

%% auth.ldap.filters.1.key = "objectClass"
%% auth.ldap.filters.1.value = "mqttUser"
%% auth.ldap.filters.1.op = "and"
%% auth.ldap.filters.2.key = "uiAttr"
%% auth.ldap.filters.2.value "someAttr"
%% auth.ldap.filters.2.op = "or"
%% auth.ldap.filters.3.key = "someKey"
%% auth.ldap.filters.3.value = "someValue"
%% The configuratation structure sent to the application:
%%   [{"objectClass","mqttUser"},"and",{"uiAttr","someAttr"},"or",{"someKey","someAttr"}]
%% The resulting LDAP filter would look like this:
%% ==> "|(&(objectClass=Class)(uiAttr=someAttr)(someKey=someValue))"
{translation, "emqx_auth_ldap.filters",
fun(Conf) ->
        Settings = cuttlefish_variable:filter_by_prefix("auth.ldap.filters", Conf),
        Keys = [{Num, {key, V}} || {["auth","ldap","filters", Num, "key"], V} <- Settings],
        Values = [{Num, {value, V}} || {["auth","ldap","filters", Num, "value"], V} <- Settings],
        Ops = [{Num, {op, V}} || {["auth","ldap","filters", Num, "op"], V} <- Settings],
        RawFilters = Keys ++ Values ++ Ops,
        Filters =
            lists:foldl(
              fun({Num,{T,V}}, Acc)->
                      maps:update_with(Num,
                                       fun(F)->
                                               maps:put(T,V,F)
                                       end,
                                       #{T=>V}, Acc)
              end, #{}, RawFilters),
        Order=lists:usort(maps:keys(Filters)),
        lists:reverse(
          lists:foldl(
            fun(F,Acc)->
                    case F of
                        #{key:=K, op:=Op, value:=V} -> [Op,{K,V}|Acc];
                        #{key:=K, value:=V} -> [{K,V}|Acc]
                    end
            end,
            [],
            lists:map(fun(K) -> maps:get(K, Filters) end, Order)))
end}.

{mapping, "auth.ldap.filters.$num.key", "emqx_auth_ldap.filters", [
    {datatype, string}
]}.

{mapping, "auth.ldap.filters.$num.value", "emqx_auth_ldap.filters", [
    {datatype, string}
]}.

{mapping, "auth.ldap.filters.$num.op", "emqx_auth_ldap.filters", [
    {datatype, {enum, [ "or", "and" ] } }
]}.


{mapping, "auth.ldap.bind_as_user", "emqx_auth_ldap.bind_as_user", [
  {default, false},
  {datatype, {enum, [true, false]}}
]}.

{mapping, "auth.ldap.username.attributetype", "emqx_auth_ldap.username_attr", [
  {default, "uid"},
  {datatype, string}
]}.

{mapping, "auth.ldap.password.attributetype", "emqx_auth_ldap.password_attr", [
  {default, "userPassword"},
  {datatype, string}
]}.
